Shark Attack is intended to give you an example of a complete game that uses SpriteWorld. It demonstrates several things the other demos don't, such as collision detection, how to add and remove sprites on the fly, how to set up an animation that may be run more than once, and how to handle the stats area of a game.
A lot could be done to improve the game and make it more enjoyable, but doing so would add quite a bit to the length and complexity of the source code, which would defeat the purpose of this demo. Shark Attack is intended to be a bare-bones version of a game, with enough there to demonstrate several important aspects of SpriteWorld, but without all of the extra stuff that would be needed in order to make it fun.
Instructions:
The plot of the game is very simple: move your submarine around and shoot the fish. Use either the arrow keys or the numeric keypad to move, and either shift or the 0 key on the keypad to shoot. You can pause the game with Command-P, or end the game with Command-E or Esc.
The small sharks take two shots to kill; the large sharks take fifteen (and they follow you too!) You have three extra lives. You advance levels after every 500 points, although the levels are currently all the same.
Thanks to:
- Robert Metzker (pen@inferno.com) for the shark artwork.
- Karl Bunker for the submarine artwork.
- Douglas Vess (dvess@grafxfactory.com) for letting me use his "sea floor" background picture. It was originally designed for use with web pages, but when I saw it, I knew it would be perfect for Shark Attack. Visit www.grafxfactory.com for more examples of his work.
- Alan Lau (alau@nortel.ca) for allowing me to use one of his ppats. For more examples of his artwork, take a look at the Mac shareware game Monkey Shines. (Available at www.fantasoft.com.) BTW, I designed two of the worlds for this game. :-)
Techniques used in Shark Attack
In this section I will list each file used in the Shark Attack project and what it does. Reading this should help give you a much better idea of what's going on in the source code.
Application.c
This is where everything starts. Application.c creates the menu bar, window, and the SpriteWorld, and calls functions to load all the sounds, patterns, and sprites. It also contains the main event loop, and the functions to handle the Title Screen animation.
If you take a look at ExitSharkAttack(), which is called when Quit is selected from the File menu, you'll notice that the functions for disposing the SpriteWorld and the sounds aren't called, since the system will dispose them anyway, and disposing them ourselves could take a long time if there is a lot of stuff that needs disposing (such as a lot of sprites). By skipping all that stuff, we can make the program quit instantly.
If you look at main(), you'll see that the window is created and shown before SetUpSpriteWorld is called. This is very important, because the window uses a custom palette (see the pltt resource in the resource file), and in order for the SpriteWorld's graphics to load properly, the window must be shown before the SpriteWorld is created. This is because the window's GDevice is used by the SpriteWorld, and then by the Sprites when they are loaded, and the window must be shown before the corresponding palette will be set. Adding a palette to your game is very easy - you simply add a pltt resource corresponding to your window's resource ID, and everything will be taken care of for you.
Shark Attack.c
This file contains all the functions for creating the SpriteWorld, disposing the SpriteWorld, and running the animation once the game starts, as well as processing keyDown and keyUp events while the game is being played. It also contains all the global variables, which the other source files access by #including GlobalVariables.h, which contains extern statements for all the global variables.
At the beginning of the source file, the maximum size of the SpriteWorld is defined. Currently, the limit is set to 640x480. If you are running on a 680x480 monitor, try setting it to something like 512x384. This will demonstrate how the game handles screen sizes larger than the SpriteWorld by filling up the extra space with a pattern. See the SetUpWindowRegions routine in Application.c to see where the regions are made that are used when drawing the patterns.
The function NewGame is called from Application.c when the user selects New Game from the File menu. This function then calls PrepareGame, RunGame, and then CleanUpAfterGame. PrepareGame does everything needed to get ready for the game, such as hiding the menu bar and the mouse cursor, and initializing the variables used during the game (such as the number of lives left). Likewise, CleanUpAfterGame prepares for the normal Title Screen animation once the game is over. RunGame is where all the action occurs; it contains the calls to SWProcessSpriteWorld, SWAnimateSpriteWorld, and the SWCollideSpriteLayer calls.
SetUpSpriteWorld is a little different from the other demos in that it locks the SpriteWorld before any of the sprites are added to it. It can do this because the Sprites are locked as soon as they are loaded (see LoadSprites in NewSprite.c).
NewSprite.c
This file contains the functions for loading and disposing all the sprites, as well as functions for cloning those sprites and adding them to the animation. In an animation where you need to be able to add and remove sprites on the fly, the best method is generally to load one copy of each sprite before the animation starts, and then use these "master" sprites to make clones using SWCloneSprite whenever you need to add that sprite to the animation. This enables you to easily add as many of a particular sprite to the animation as you need to. The master sprites are never added to the animation themselves; just their clones, so that you can always remove and dispose any sprite that is in the animation without disposing the master sprite.
The function LoadSprites loads all the master sprites. Most of the other functions, such as NewSubSprite, NewBulletSprite, or NewFishSprite, make a clone of the master sprite, extend the cloned sprite's record to include information specific to that sprite, set up the variables of the sprite, set the sprite's MoveProc, add it to the proper layer, and return the SpritePtr to the caller. The caller can then set any additional characteristics of the sprite that aren't constant, such as the sprite's delta and starting position. NewSprite.h contains the custom structures for each sprite that are used when extending the sprite's record. (For more information on how to extend a sprite's record, see the SpriteWorld FAQ.)
SpriteMoveProcs.c
This file contains the movement procedures that are used to move each type of sprite. They are pretty self-explanatory. I wouldn't spend too much time studying the movement procedures; they are likely to be confusing to anyone who didn't create them. Also, you may wonder why I use floating point variables to keep track of the sprite's current position and speed, instead of using fixed-point variables. The answer is simply that floating point variables are easier to use, and in this case, I don't need them to be particularly fast, so I didn't bother with fixed-point variables.
SpriteCollideProcs.c
This file is also pretty self-explanatory. It contains the routines that are called when a collision is detected between two sprites.
Level.c
This file contains functions that are called while the game is being played that keep adding fish and sharks to the animation. It also contains functions that are called when you die, when the level is completed, and when the game is over.
Sounds.c
Rather than use a sound kit, I decided to try making my own sound routines that simply call SndPlay. It turned out to be quite easy to do, and Sounds.c is the result. Shark Attack creates three "multi-purpose" channels that can be used by any sound, and one channel that is reserved specifically for the gun's shooting sound.
The function PlaySound is used to play a sound in one of the multi-purpose channels. The sounds for when a fish is hit or killed get played in these channels. The function accepts two parameters; one is a soundID, which is simply a number referring to the sound that should be played, and the second parameter is a number indicating the requested channel that the sound should be played in. If the requested channel is full, PlaySound looks for an empty channel, and if it finds one, it plays the sound in that channel. If all channels are full, then it stops the sound that was previously playing in the requested channel and starts playing the new sound in it.
The function PlayGunSound is used to play the gun's sound, which is done whenever the submarine shoots a bullet. If a sound is already playing in this channel (from the last time the gun was shot), it is stopped, and the new sound is started.
Special Effects.c
This file contains only one special effect - a "wipe" effect that's a little more fancy than simply updating the SpriteWorld the normal way.
Stats.c
This file contains the routines for handling the "stats" area of the game. (That is, the area at the top of the screen that displays the number of lives left, the current level, and your score.)
GlobalVariables.h
This is a very important file that contains extern statements that make the global variables that are defined in Shark Attack.c available to any file that includes this header.
Questions? Comments? Feel free to write to me at Jensen@loop.com.